// http://en.wikipedia.org/wiki/Edge_detection
// http://en.wikipedia.org/wiki/Canny_edge_detector
// http://www.pages.drexel.edu/~weg22/can_tut.html
// http://www.cs.bham.ac.uk/research/projects/poplog/popvision/help/canny
// http://www.cs.uiowa.edu/~cwyman/classes/spring08-22C251/homework/canny.pdf
// http://www.kilometer0.com/edge_detection.php
// http://lukewalsh.co.uk/blog/2008/05/real-time-edge-detection-in-flash.html
// http://lukewalsh.co.uk/blog/2008/06/sobel-edge-detection-in-flash.html
// http://me.queensu.ca/people/sellens/research/sprayFlow/mcleod/research/imagepro.htm

package
{
	import flash.display.BitmapData;
	import flash.filters.ColorMatrixFilter;
	import flash.filters.ConvolutionFilter;
	import flash.geom.Point;
	
	public class EdgeDetection {

		//There are a number of different ways to convert a colour image to 
		// greyscale, it really doesn't matter which one we choose too much. 
		// A simple greyscale will be used. The average of the red, 
		// green and blue colour channels will be put on the blue colour 
		// channel, this will lead to a 'bluescale' image, which will be used 
		// for speed.
		public static function bluescaleFilter(snapshotData:BitmapData):BitmapData {
			var grayscale:ColorMatrixFilter = new ColorMatrixFilter(
				[0,  0,  0,  0,  0, 
				 0,  0,  0,  0,  0, 
				 1/3, 1/3, 1/3, 0, 0, 
				 0, 0, 0, 1, 0]);
			snapshotData.applyFilter(snapshotData,snapshotData.rect,new Point(),grayscale);
			return snapshotData;
		}

//-----------------------------------------------------------------------------

		public static function bluescaleFilterPixel(pixel:int):int {
			return ((pixel & 0xFF0000) >> 16) / 3 + 
				   ((pixel & 0x00FF00) >> 8 ) / 3 + 
				   (pixel & 0x0000FF) / 3;
		}
		
//-----------------------------------------------------------------------------
	
		private static function applyFilter(snapshotData:BitmapData, matrix:Array, width:int, height:int, divisor:int, bias:int):BitmapData {
			var convolutionFilter:ConvolutionFilter = new ConvolutionFilter(width,height, matrix, divisor, bias ); 
			snapshotData.applyFilter(snapshotData, snapshotData.rect, new Point(), convolutionFilter);
			return snapshotData;
		}

//-----------------------------------------------------------------------------
	
		// The notes from Adobe show that a 3x3 filter within certain limits 
		// will work significantly faster on most computers than others.
		// Using a 3x3 Gaussian over a 5x5 saves half a second.
		public static function smothFilter3x3(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [ 1,  2,  1,
							 				2,  4,  2,
							  				1,  2,  1];
			return applyFilter(snapshotData, convolutionMatrix, 3, 3, 16, 0);
		}

//-----------------------------------------------------------------------------

		public static function smothFilter3x3Pixel(pixelNW:int, pixelN:int, 
												   pixelNE:int, pixelW:int, 
												   pixel:int, pixelE:int,
												   pixelSW:int, pixelS:int,
												   pixelSE:int):int {
			return 1 * pixelNW + 2 * pixelN + 1 * pixelNE + 2 * pixelW +
				   4 * pixel + 2 * pixelE + 1 * pixelSW + 2 * pixelS + 1 * pixelSE;
		}
		
//-----------------------------------------------------------------------------
		
		public static function smothFilter5x5(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [ 2,  4,  5,  4,  2,
							 				4,  9, 12,  9,  4,
							  				5, 12, 15, 12,  5,
							  				4,  9, 12,  9,  4,
							  				2,  4,  5,  4,  2];
			return applyFilter(snapshotData, convolutionMatrix, 5, 5, 115, 0);
		}

//-----------------------------------------------------------------------------

		// The convolution filter is offset by 127 so that both positive 
		// (white to black) and negative (black to white) edge responses 
		// can be stored in the image.
		public static function sobelFilterX(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [ -1,  0,  1,
							 				-2,  0,  2,
							  				-1,  0,  1 ];
			return applyFilter(snapshotData, convolutionMatrix, 3, 3, 1, 127);
		}

//-----------------------------------------------------------------------------

		public static function sobelFilterXPixel(pixelNW:int, pixelN:int, 
												   pixelNE:int, pixelW:int, 
												   pixel:int, pixelE:int,
												   pixelSW:int, pixelS:int,
												   pixelSE:int):int {
			return (-1) * pixelNW + 1 * pixelNE + (-2) * pixelW +
				   2 * pixelE + (-1) * pixelSW + 1 * pixelSE;
		}
		
//-----------------------------------------------------------------------------
				
		public static function sobelFilterY(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [  1,  2,  1,
							 				 0,  0,  0,
							  				-1, -2, -1 ];
			return applyFilter(snapshotData, convolutionMatrix, 3, 3, 1, 127);
		}

//-----------------------------------------------------------------------------

		public static function sobelFilterYPixel(pixelNW:int, pixelN:int, 
												   pixelNE:int, pixelW:int, 
												   pixel:int, pixelE:int,
												   pixelSW:int, pixelS:int,
												   pixelSE:int):int {
			return 1 * pixelNW + 2 * pixelN + 1 * pixelNE +
				   (-1) * pixelSW + (-2) * pixelS + (-1) * pixelSE;
		}

//-----------------------------------------------------------------------------
		
		public static function scharrFilterX(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [  3,  0, -3,
							 				10,  0,-10,
							  				 3,  0, -3 ];
			return applyFilter(snapshotData, convolutionMatrix, 3, 3, 1, 127);
		}

//-----------------------------------------------------------------------------

		public static function scharrFilterY(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [  3,  10, 3,
							 				 0,  0,  0,
							  				-3,-10, -3 ];
			return applyFilter(snapshotData, convolutionMatrix, 3, 3, 1, 127);
		}
		
//-----------------------------------------------------------------------------

		public static function nonMaximaSupressionFilter(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [  3, 2, 1,
							 				 0, 0, 0,
							  				 1, 2, 3 ];
			return applyFilter(snapshotData, convolutionMatrix, 3, 3, 1, 0);
		}

//-----------------------------------------------------------------------------

		public static function thresholdFilter(snapshotData:BitmapData):BitmapData {
			var convolutionMatrix:Array = [  3, 2, 1,
							 				 0, 0, 0,
							  				 1, 2, 3 ];
			return applyFilter(snapshotData, convolutionMatrix, 3, 3, 1, 0);
		}
		
//-----------------------------------------------------------------------------

		public static function gradientMagnitudeFilter(snapshotData:BitmapData):BitmapData {
			var snapshotDataX:BitmapData = sobelFilterX( snapshotData.clone() );
			var snapshotDataY:BitmapData = sobelFilterY( snapshotData.clone() );
			var pixel:int = 0;
			var pixelX:int;
			var pixelY:int;
			var lowerThreshold:int = 0 + 120;
			var upperThreshold:int = 0xff - 120;
			var width:int = snapshotData.width;
			var height:int = snapshotData.height;

			for( var x:int = 0; x < width; x++) {
        		for( var y:int = 0; y < height; y++ ) {
					//Just using blue channel.
        			pixelX = (snapshotDataX.getPixel(x,y) & 0xff);
        			pixelY = (snapshotDataY.getPixel(x,y) & 0xff);
        			// Check threshold
        			if( !(pixelX > lowerThreshold && pixelX < upperThreshold && pixelY > lowerThreshold && pixelY < upperThreshold)){
        				// The 127 offset is removed so that the edge responses 
        				// are now positive and negative.
        				pixelX = pixelX - 127;
        				pixelY = pixelY - 127;
        				pixel = Math.abs( pixelX ) + Math.abs( pixelY );
        				// Minus one because otherwise max value is 256
        				pixel = pixel - 1;
        				pixel = (pixel << 16) + (pixel << 8) + pixel;
        				snapshotData.setPixel( x, y, pixel);
        			}else{
        				snapshotData.setPixel( x, y, 0);
        			}
        		}
   			}
			return snapshotData;
		}

//-----------------------------------------------------------------------------

		public static function fastGradientMagnitudeFilter(snapshotData:BitmapData):BitmapData {
			var pixelNW:int = 0;
			var pixelN:int = 0;
			var pixelNE:int = 0;
			var pixelW:int = 0;
			var pixel:int = 0;
			var pixelE:int = 0;
			var pixelSW:int = 0;
			var pixelS:int = 0;
			var pixelSE:int = 0;
			var pixelX:int = 0;
			var pixelY:int = 0;
			var pixelOut:int = 0;
			var lowerThreshold:int = 0 + 120;
			var upperThreshold:int = 0xff - 120;
			var width:int = snapshotData.width;
			var height:int = snapshotData.height;
			var snapshotDataOut:BitmapData = new BitmapData(width, height, false, 0);

			// All to blue scale
			snapshotData = EdgeDetection.bluescaleFilter( snapshotData );
			// Smoth
			snapshotData = EdgeDetection.smothFilter3x3( snapshotData );
			
			for( var x:int = 1; x < width - 1; x++) {
        		for( var y:int = 1; y < height - 1; y++ ) {
					pixelNW = snapshotData.getPixel( x - 1 , y - 1 );
					pixelN  = snapshotData.getPixel( x     , y - 1 );
					pixelNE = snapshotData.getPixel( x + 1 , y - 1 );
					pixelW  = snapshotData.getPixel( x - 1 , y     );
					pixel   = snapshotData.getPixel( x     , y     );
					pixelE  = snapshotData.getPixel( x + 1 , y     );
					pixelSW = snapshotData.getPixel( x - 1 , y + 1 );
					pixelS  = snapshotData.getPixel( x     , y + 1 );
					pixelSE = snapshotData.getPixel( x + 1 , y + 1 );
					
					pixelX = sobelFilterXPixel(pixelNW, pixelN, pixelNE,
												pixelW, pixel, pixelE, 
												pixelSW, pixelS, pixelSE);
					pixelX = pixelX & 0xFF;
					pixelX = pixelX + 127;
					pixelY = sobelFilterYPixel(pixelNW, pixelN, pixelNE,
												pixelW, pixel, pixelE, 
												pixelSW, pixelS, pixelSE);
					pixelY = pixelY & 0xFF;
					pixelY = pixelY + 127;
					
					if( !(pixelX > lowerThreshold && pixelX < upperThreshold && pixelY > lowerThreshold && pixelY < upperThreshold)){
						pixelX = pixelX - 127;
        				pixelY = pixelY - 127;
						// insert comment
						pixelOut = Math.abs( pixelX ) + Math.abs( pixelY );
						// Minus one because otherwise max value is 256
        				pixelOut = pixelOut - 1;
        				pixelOut = (pixelOut << 16) + (pixelOut << 8) + pixelOut;
        				snapshotDataOut.setPixel( x, y, pixelOut);
     				}else{
     					snapshotDataOut.setPixel( x, y, 0);
     				}
     				

        		}
   			}
			return snapshotDataOut;
		}
		
//-----------------------------------------------------------------------------
		
		public static function gradientAngleFilter(snapshotData:BitmapData):BitmapData {
			var snapshotDataX:BitmapData = sobelFilterX( snapshotData.clone() );
			var snapshotDataY:BitmapData = sobelFilterY( snapshotData.clone() );
			var width:int = snapshotData.width;
			var height:int = snapshotData.height;
			var angle:Number = 0;

			for( var x:int = 0; x < width; x++) {
        		for( var y:int = 0; y < height; y++ ) {
					angle = Math.atan2( 
								snapshotDataY.getPixel(x,y), 
								snapshotDataX.getPixel(x,y) ) 
							/ (Math.PI/180);
					
					if( angle > 0 && angle < 22.5 )
						snapshotData.setPixel(x,y, 0xFFFF00 );
					else if( angle > 22.5 && angle < 67.5 )
						snapshotData.setPixel(x,y, 0x00FF00 );
					else if( angle > 67.5 && angle < 112.5 )
						snapshotData.setPixel(x,y, 0x0000FF );
					else if( angle > 112.5 && angle < 157.5 )
						snapshotData.setPixel(x,y, 0xFF0000 );
					else if( angle > 157.5 && angle < 202.5 )
						snapshotData.setPixel(x,y, 0xFFFF00 );
					else if( angle > 202.5 && angle < 247.5 )
						snapshotData.setPixel(x,y, 0x00FF00 );
					else if( angle > 247.5 && angle < 292.5 )
						snapshotData.setPixel(x,y, 0x0000FF );
					else if( angle > 292.5 && angle < 337.5 )
						snapshotData.setPixel(x,y, 0xFF0000 );
					else
						snapshotData.setPixel(x,y, 0xFFFF00 );
						
        		}
   			}
			return snapshotData;
		}

	}

}